Intro
In this blog I’ll be covering a recent phishing campaign that was targeting content creators while impersonating to a brand offering a collaboration offer to those creators.
The Phish
The email that the user receives includes a short explanation that the company wants to be his partner, they explain to him when and for how long to put the promo video and of course how much money he will receive as a payment.
At the bottom of the email the user will find a link to the promotion materials and his personal password:
The promotion materials link leads to Google Drive, there the User will need to download an archive with the name of: H&M Corporation Advertising Contract.zip
The archive contains inside of it several decoy files that are associated with H&M, and a 600MB .scr file with the name: H&M Advertising contract and Payment information.pdf.scr
.NET Loader
Opening the loader in DiE,we can see that the loader is 32bit .NET assembly
protected with Smart Assembly
:
I’ve opened the loader in DnSpy
to further analyze it. The first thing I see is the confirmation that the loader is protected with Smart Assembly
, I can see the PoweredBy section in the static information fields:
Looking at the entry point we can understand that working with the loader in this state won’t be efficient:
I will be using SAE (Simple Assembly Explorer) in order to deobfuscate the code, we can use the deobfuscator
feature in SAE:
I’m using the default settings as it’s fits my needs:
Opening the deoubfuscated output file in Dnspy, we can now see a clearer code:
Payload Extraction
There are several interesting actions that happens in the loader:
c000009
instance creation with internal field that will contain a path to the injected process.
-
The instance then will be passed to the method
c000066.m000022
. this method will have several things in it, the first one being a call to the method:c000066.m00007b
, passing the string: fInckSommmenn twice. -
The method
c000066.m00007b
will simply fetch resource content from the binary resources:
-
Then a call to the method
c000066.m000019
will be invoked passing the extracted resource content, the string: fInckSommmenn and the instance ofc000009
-
This method will be in charge of decrypting the payload with some Xor routine and it will return the decrypted binary.
- After the decryption was done the decrypted binary will be passed alongside with the full path to the injected process to
c000066.m00002a
method which will do a process injection to the desired process with the decrypted binary content.
I’ve created a powershell script that extract the decrypted binary by invoking the necessery methods:
# Load the file.
$assembly = [System.Reflection.Assembly]::LoadFile("C:\Users\igal\Desktop\loader.exe")
#Initialize "NS005.c000009" object.
$ini = [Activator]::CreateInstance($assembly.Modules[0].GetType("NS005.c000009"),@())
#Retrieve the resource fetching method and invoke it.
$classType2 = $assembly.GetType("NS004.c000066")
$array = $classType2.GetMethod("m00007b").Invoke($null,@("fInckSommmenn", "fInckSommmenn"))
#Invoke the decryption method with the necessary arguments.
$fixedArray = $classType2.GetMethod("m000019").Invoke($null,@($array, "fInckSommmenn", $ini))
#Write the output to a file.
[io.file]::WriteAllBytes('C:\Users\igal\Desktop\payload.bin',$fixedArray)
Vidar Payload
In this part of the blog I will be going through some of the Vidar stealer capabilities, evasion techniques and some anti analysis tricks.
Opening the payload in DiE we can see that it’s a 32bit C/C++
binary:
Anti-Analysis Nightmare
I’ve opened the payload in IDA and the first thing that happend is that WinMain was not recognized as a function and rather as instruction:
I’ve tried to convert it to function by pressing P
but this wasn’t helpful, so I’ve scrolled a bit down and found out a chunk of data that wasn’t convered as supposed:
Then I pressed C
to convert that data to code and now that we have instructions instead of data I’ve marked all the instruction from the beginning of WinMain until the relevent mov - pop - return
instructions that marks the end of a function (in my case the instructions range was 0x4156B0 - 0x415891
)
Now I start to work with the decompiler view, I’ve noticed that the decompilation process is a bit broken:
One thing that was done here to confuse the decompiler is Opaque Predicate.
“Opaque predicate is a term used in programming to refer to decision making where there is only one possible outcome. This can be achieved through the use of complex or hard-to-understand logic, such as calculating a value that will always return True. Opaque predicates are often used as anti-disassembling techniques, as they can make it difficult for an analyst to understand the code and determine its intent. By using opaque predicates, malware authors can make their code more difficult to reverse engineer, which can help to evade detection and analysis.” (Unprotect Project definition)
We can use @_n1ghtw0lf script for it:
import idc
ea = 0
while True:
ea = min(idc.find_binary(ea, idc.SEARCH_NEXT | idc.SEARCH_DOWN, "74 ? 75 ?"), # JZ / JNZ
idc.find_binary(ea, idc.SEARCH_NEXT | idc.SEARCH_DOWN, "75 ? 74 ?")) # JNZ / JZ
if ea == idc.BADADDR:
break
idc.patch_byte(ea, 0xEB) # JMP
idc.patch_byte(ea+2, 0x90) # NOP
idc.patch_byte(ea+3, 0x90) # NOP
After running the script the Decomplier looks a bit better:
But there is still some code missing because we can see a JUMPOUT instruction, looking at the referenced address in the instruction, we can see that the instruction is:
mov eax, 0FEB912E8h
clearly that’s wrong and nothing to do with the actual code (and this is caused because the convertation of all the data to code), it can be repaired by simply undefining the instruction. But after that we still can see a unclear jumpout:
again same strange mov instrcution to eax:
mov eax, 0FEB9C8E8h
it can be fixed by the same approach as before.
After clearing the code we have a “clear” function:
The Author added a lot of junk calls to the code to make our life a bit harder but we can just ignore them and follow the function calls.
Self Termination Triggers
This Vidar payload has several triggers that can occur and lead to self termination of the payload.
- The first one being usage of
VirtualAllocExNuma
which is a way for the payload to understand whether he runs on a system with one or more physical CPU:
- The second check the payload does is checking the physical memory of the computer (whether it’s above 769MB or not) if it’s less then the defined size the payload will terminate:
- The last check will occur after the strings and api resolving functions(which will be covered in a moment), it will retrieve the computer name and compare it to
HAL9TH
, it will also retrieve the user name and compare it toJohnDoe
. if one of the retrieved values matches one of the strings the payload will terminate itself:
Strings Decryption
As most variants of Vidar, the strings are simply xor’ed. The function receives 3 parameters:
- Length
- Xor key
- Encrypted string
I’ve used the script written by @eln0ty and modified it abit to fit my needs:
import idc
START = 0x401190
END = 0x40134D
TEMP = 0x0
FLAG = True
'''
[0] = Encrypted String.
[1] = Xor Key.
[2] = Length.
'''
VALUES = []
ea = START
# XOR decryption helper function.
def xorDecrypt(encString, xorKey, keyLen):
decoded = []
for i in range(0,len(encString)):
decoded.append(encString[i] ^ xorKey[i % keyLen])
return bytes(decoded)
while ea <= END:
# get argument values
if idc.get_operand_type(ea, 0) == idc.o_imm:
VALUES.append(idc.get_operand_value(ea, 0))
if len(VALUES) == 2:
if idc.get_operand_type(ea, 0) == idc.o_reg:
VALUES.append(idc.get_operand_value(ea, 1))
if idc.print_insn_mnem(ea) == "call":
length = VALUES[2]
data = idc.get_bytes(VALUES[0], length)
key = idc.get_bytes(VALUES[1], length)
VALUES = []
TEMP = ea
while FLAG:
ea = idc.next_head(ea, END)
if (idc.print_insn_mnem(ea) == "mov") and (idc.get_operand_type(ea, 0) == idc.o_mem) and (idc.get_operand_type(ea, 1) == idc.o_reg):
dec = xorDecrypt(data, key, length).decode('ISO-8859-1')
print(f'current location:{hex(ea)}, value will be: {dec}')
dwordVar = idc.get_operand_value(ea, 0)
idc.set_cmt(ea, dec, 1)
idc.set_name(dwordVar, "STR_" + dec, SN_NOWARN)
FLAG = False
ea = TEMP
break
# move to next instruction
FLAG = True
ea = idc.next_head(ea, END)
quick note: some of the names wont be assigned properly due to IDA syntax, so I’ve added the plain string as comment in the dissembler. For example:
Decoded strings output:
Dynamic API Resolving:
Vidar will user LoadLibraryA
and GetProcAddress
to resolve the necessery API’s alongside with the strings it decrypted:
Once again I used the script written by @eln0ty to replace the name of the variables for easier analysis:
import idc
start = 0x420874
end = 0x420901
ea = start
api_names = []
while ea <= end:
# get GetProcAddress API name
if (idc.print_insn_mnem(ea) == "mov") and (idc.get_operand_type(ea, 0) == idc.o_reg) and (idc.get_operand_type(ea, 1) == idc.o_mem):
addr = idc.get_operand_value(ea, 1)
name = idc.get_name(addr)
if name.startswith("STR_"):
api_names.append(name)
# assign GetProcAddress result to global var
if (idc.print_insn_mnem(ea) == "mov") and (idc.get_operand_type(ea, 0) == idc.o_mem) and (idc.print_operand(ea, 1) == "eax"):
addr = idc.get_operand_value(ea, 0)
name = api_names.pop(0)
idc.set_name(addr, "API_" + name[4:])
# move to next instruction
ea = idc.next_head(ea, end)
C2 Communication - Init Communication
In order to harvest all the data Vidar looking for, Vidar will need to utilize some DLL’s which it will fetch from a C2 server, below is a short explanation of the DLL’s Vidar will retrieve from the C2:
DLL Name | Description |
---|---|
freebl3.dll | Network Security Services (NSS) from Mozilla Foundation |
mozglue.dll | Memory management for Mozilla applications |
msvcp140.dll | Microsoft Visual C++ library for C++ programming |
nss3.dll | Network security services for SSL/TLS encryption |
softokn3.dll | Cryptographic library for key management and encryption/decryption |
sqlite3.dll | Accessing and managing SQLite databases |
vcruntime140.dll | Microsoft Visual C++ library for memory management and I/O |
In my case the Vidar C2 was hosted on 2 different sites:
- Telegram:
- Steam:
And in case both of them are down, a plain C2 is presented as a backup:
After retrieving the C2 Vidar will send a POST
request to the URI:
{C2}/{BOT_ID}
In my case the bot id is: 907
which is also assigned a plain string:
After that first request was made the client will receive a response from the server that looks like that:
1,1,1,1,1,b36abae611984b4404a903d57724b39e,1,1,1,1,0,123;%DOCUMENTS%\;*.txt;50;true;movies:music:mp3:exe;
Each operation is splitted with ;
delimiter
C2 Communication - Operations Configuration
As mentioned, each operation is splitted by ;
delimiter.
First Section:
1,1,1,1,1,b36abae611984b4404a903d57724b39e,1,1,1,1,0,123
Most of those values are flags that says what data should be harvested: |Index|Flag|Description| | — | — | — | |1|1|Local Passwords| |2|1|Cookies| |3|1|Crypto Wallets| |4|1|Browser History| |5|1|Telegram Data| |6|b36abae611984b4404a903d57724b39e|Exfil Token| |7|1|Steam Data| |8|1|Discord Data| |9|1|Screenshot| |10|1|Possible Grabber| |11|0|File Size Limit| |12|123|Profile ID|
Second Section:
%DOCUMENTS%\
The grabber activity folder.
Third Section:
*.txt
Files extensions the grabber will harvest.
Fourth Section:
50
File size limit in KB.
Fifth Section:
true
Recursive harvesting.
Sixth Section:
movies:music:mp3:exe
Excluded file extensions.
Additionally Vidar will create a profile for the user by harvesting the OS info, RAM, CPU, active processes etc… and will send out infromation.txt
alongside with the harvested data:
Version: 2.4
Date: 12/2/2023 11:15:46
MachineID: 4cfb5922-b036-4c14-9ed1-03c0dad19fbd
GUID: {d6dc608d-2a27-11ed-a0e3-806e6f6e6963}
HWID: 12ac9eab3d083674480464-4cfb5922-b036-4c14-9ed1-a0e3-806e6f6e6963
Path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\vbc.exe
Work Dir: In memory
Windows: Windows 10 Pro [x64]
Install date: 8/12/2021 0:18:31
AV: Unknown
Computer Name: IYMUGYHL
User Name: Admin
Display Resolution: 1280x720
Display Language: en-US
Keyboard Languages: English (United States)
Local Time: 12/2/2023 11:15:47
TimeZone: UTC-0
[Hardware]
Processor: Intel Core Processor (Broadwell)
Cores: 2
Threads: 2
RAM: 4095 MB
VideoCard: Microsoft Basic Display Adapter
[Processes]
- System [4]
- Registry [92]
- smss.exe [348]
- csrss.exe [436]
- wininit.exe [512]
- csrss.exe [520]
- winlogon.exe [604]
- services.exe [644]
- lsass.exe [656]
- fontdrvhost.exe [764]
- fontdrvhost.exe [772]
- svchost.exe [780]
- svchost.exe [884]
- svchost.exe [932]
- dwm.exe [1016]
- svchost.exe [60]
- svchost.exe [720]
- svchost.exe [640]
- svchost.exe [1044]
- svchost.exe [1052]
- svchost.exe [1140]
- svchost.exe [1192]
- svchost.exe [1208]
- svchost.exe [1232]
- svchost.exe [1316]
- svchost.exe [1384]
- svchost.exe [1432]
- svchost.exe [1452]
- svchost.exe [1504]
- svchost.exe [1572]
- svchost.exe [1604]
- svchost.exe [1616]
- svchost.exe [1712]
- svchost.exe [1740]
- svchost.exe [1840]
- svchost.exe [1876]
- svchost.exe [1900]
- svchost.exe [1952]
- svchost.exe [1968]
- spoolsv.exe [1296]
- svchost.exe [1944]
- svchost.exe [2064]
- svchost.exe [2100]
- sihost.exe [2288]
- svchost.exe [2296]
- taskhostw.exe [2436]
- svchost.exe [2488]
- svchost.exe [2496]
- OfficeClickToRun.exe [2552]
- svchost.exe [2560]
- svchost.exe [2616]
- svchost.exe [2656]
- svchost.exe [2668]
- svchost.exe [2676]
- svchost.exe [2976]
- explorer.exe [3048]
- svchost.exe [2832]
- dllhost.exe [3248]
- StartMenuExperienceHost.exe [3356]
- RuntimeBroker.exe [3416]
- dllhost.exe [3456]
- SearchApp.exe [3568]
- RuntimeBroker.exe [3688]
- RuntimeBroker.exe [4652]
- svchost.exe [4340]
- svchost.exe [1892]
- svchost.exe [3392]
- svchost.exe [4424]
- svchost.exe [4680]
- sppsvc.exe [1096]
- svchost.exe [1260]
- svchost.exe [2544]
- WmiPrvSE.exe [1348]
- SppExtComObj.Exe [2532]
- svchost.exe [2596]
- svchost.exe [3020]
- upfc.exe [4400]
- svchost.exe [1632]
- H&M Advertising contract and Payment information.pdf.scr [4396]
- vbc.exe [1684]
[Software]
Google Chrome [89.0.4389.114]
Microsoft Edge [92.0.902.67]
Microsoft Edge Update [1.3.167.21]
Microsoft Visual C++ 2012 Redistributable (x86) - 11.0.61030 [11.0.61030.0]
Java Auto Updater [2.8.66.17]
Microsoft Visual C++ 2015-2022 Redistributable (x86) - 14.30.30704 [14.30.30704.0]
Microsoft Visual C++ 2015-2022 Redistributable (x64) - 14.30.30704 [14.30.30704.0]
Microsoft Visual C++ 2013 Redistributable (x86) - 12.0.40660 [12.0.40660.0]
Microsoft Visual C++ 2013 x86 Additional Runtime - 12.0.40660 [12.0.40660]
Microsoft Visual C++ 2008 Redistributable - x86 9.0.30729.6161 [9.0.30729.6161]
Adobe Acrobat Reader DC [19.010.20069]
Microsoft Visual C++ 2012 x86 Additional Runtime - 11.0.61030 [11.0.61030]
Microsoft Visual C++ 2012 x86 Minimum Runtime - 11.0.61030 [11.0.61030]
Microsoft Visual C++ 2022 X86 Additional Runtime - 14.30.30704 [14.30.30704]
Microsoft Visual C++ 2012 Redistributable (x64) - 11.0.61030 [11.0.61030.0]
Microsoft Visual C++ 2013 x86 Minimum Runtime - 12.0.40660 [12.0.40660]
Microsoft Visual C++ 2013 Redistributable (x64) - 12.0.40660 [12.0.40660.0]
Microsoft Visual C++ 2010 x86 Redistributable - 10.0.40219 [10.0.40219]
Microsoft Visual C++ 2022 X86 Minimum Runtime - 14.30.30704 [14.30.30704]
C2 Communication - Data Exfiltration
After harvesting all the data Vidar will compress all harvested data to as a zip encode it to base64 and send it out alongside with some more data in the next format:
------{random_generated_delimiter}
Content-Disposition: form-data; name="profile"
{BOT_ID}
------{random_generated_delimiter}
Content-Disposition: form-data; name="profile_id"
{PERSONAL_ID}
------{random_generated_delimiter}
Content-Disposition: form-data; name="hwid"
{COMPUTER_HWID}
------{random_generated_delimiter}
Content-Disposition: form-data; name="token"
{EXFIL_TOKEN}
------{random_generated_delimiter}
Content-Disposition: form-data; name="file"
{BASE64_ENCODED_ARCHIVE}
Post Exfiltration Self Termination
After Vidar exfiltrated the data it will create a self termination task using cmd command and by this will end the execution of itself:
"C:\Windows\System32\cmd.exe" /c timeout /t 6 & del /f /q Vidar.exe & exit
Summary
Vidar is a well known stealer that was active for the past years and keeps on constantly updated by its developers.
In this blog we’ve covered most Vidars functions and how it was delivered to it’s victims.
Quick note that it’s my first “In Depth” writeup for a malware so any feedback would be appreciated, you can always PM me on twitter (0xToxin)
Yara Rule
The rule is updated up to version 2.4 which was recently revamped from version 5X.X (more info can be found here)
rule win_Vidar
{
meta:
author = "0xToxin"
description = "Vidar stealer strings and functions"
Date = "20-02-2022"
strings:
$dll1 = "vcruntime140.dll" ascii wide
$dll2 = "softokn3.dll" ascii wide
$dll3 = "nss3.dll" ascii wide
$dll4 = "msvcp140.dll" ascii wide
$dll5 = "mozglue.dll" ascii wide
$dll6 = "freebl3.dll" ascii wide
$dll7 = "sqlite3.dll" ascii wide
$c2Fetch1 = "t.me" ascii wide
$c2Fetch2 = "steamcommunity.com" ascii wide
$stringDec = {
68 ?? ?? ?? 00
68 ?? ?? ?? 00
B9 ?? ?? 00 00
E8 ?? ?? ?? ??
68 ?? ?? ?? 00
68 ?? ?? ?? 00
B9 ?? ?? 00 00
A3 ?? ?? ?? ??
}
condition:
uint16(0) == 0x5a4d and 3 of ($dll*) and 1 of ($c2Fetch*) and #stringDec >= 15
}
You can see also the Yara Hunt result on UnpackMe.
IOC’s
- Samples:
- H&M Corporation Advertising Contract.zip - 4d9697358936b516ecd2dd96687649fc1a8b1e8fd4529961dfa49513c85b42c5
- H&M Advertising contract and Payment information.pdf.scr - 203b08962eba219761690043281f81fc2d6e1fa26702bfa4ad30d9849b267309
- vidar.bin - dd15f493fc13d00bb1abc0ac20bb0f7dc44632e71b4fcde1c2889fc34dff6c14
- Fetching URL’s:
- https://steamcommunity.com/profiles/76561199476091435
- https://t.me/gurutist
- C2’s:
- 195.201.44.125
- 23.88.36.149:80
- 95.216.164.28:80